<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Ocean GPU r179.1</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  <style>
    body {
      overflow: hidden;
      margin: 0;
      color: black;
      font-family: Courier;
      font-size: 20pt;
      font-weight: bold;
    }
  </style>
</head>

<body oncontextmenu="return false;">

<!--<script type="importmap">-->
<!--  {-->
<!--    "imports": {-->
<!--      "three": "https://cdn.jsdelivr.net/npm/three@0.178.0/build/three.webgpu.js",-->
<!--      "three/webgpu": "https://cdn.jsdelivr.net/npm/three@0.178.0/build/three.webgpu.js",-->
<!--      "three/tsl": "https://cdn.jsdelivr.net/npm/three@0.178.0/build/three.tsl.js",-->
<!--      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/",-->
<!--      "stats-gl": "https://cdn.jsdelivr.net/npm/stats-gl@2.2.8/dist/main.js"-->
<!--    }-->
<!--  }-->
<!--</script>-->
<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.179.1/build/three.webgpu.js",
      "three/webgpu": "https://cdn.jsdelivr.net/npm/three@0.179.1/build/three.webgpu.js",
      "three/tsl": "https://cdn.jsdelivr.net/npm/three@0.179.1/build/three.tsl.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.179.1/examples/jsm/",
      "stats-gl": "https://cdn.jsdelivr.net/npm/stats-gl@2.2.8/dist/main.js"
    }
  }
</script>

<script type="module">

  import * as THREE from "three";
  //import Stats from "three/addons/libs/stats.module.js";
  import {
    color, texture, normalMap, float, vec2, attribute, positionLocal,
    textureStore, wgslFn, instanceIndex, code, uniform, timerLocal
  } from 'three/tsl';
  import {OrbitControls} from "three/addons/controls/OrbitControls.js";
  import Stats from 'stats-gl';
  // Custom Modules
  import {Ocean} from "./Ocean.js"; // r170 with compute.aSync

  /*= VARIABLES ================================================================*/
  //-	Math Predefined
  var PieVal = Math.PI;			// PI
  var DegRad = PieVal / 180;		// Convert Degrees to Radians
  var RadDeg = 180 / PieVal;		// Convert Radians to Degrees
  //- Flags
  let LodFlg = 0;
  let PawsOn = 0;
  let WireOn = 0;
  let StatOn = 1;					// Stats ((0 = off, 1 = on)
  //= SKYBOX =====================================================================
  let SkyCol = 0x1732c1;			// Sky
  let SkyLim = 100000;			// Max viewing distance
  //- Texture
  let SBPath = "https://PhilCrowther.github.io/Aviation/textures/cube/skyboxsun25deg/";
  //= SUN ========================================================================
  let SunCol = 0xffffff;			// Sun
  let SunInt = 3;					// Intensity
  //- Rotation and Position (fixed)
  let SunLat = 23;				// Direction - Vert (+/- 90)
  let SunLon = 312;				// Direction - Horz (0->360)
  let SunDst = 10000;				// Distance (for shadows and lensflare)
  //- New
  let SunSph = new THREE.Spherical(SunDst, (90 - SunLat) * DegRad, Mod360(180 - SunLon) * DegRad);
  let SunPos = new THREE.Vector3().setFromSpherical(SunSph);
  //= GRID DATA ==================================================================
  let GrdSiz = 512;				// Size of Smallest Grid Square (meters)
  let GrdRes = 512;
  let GrdSeg = 256;				// Segments per Plane (256 = OK, 512 = too much)
  //= OCEAN ======================================================================
  //- Constants
  let WtrCol = 0x081080;			// Water (Navy)
  let WndSpd = 20;
  let WndHdg = 0;
  let Choppy = 2;
  //- Variables
  let waves = 0;
  // Uniforms
  let wav_ = {
    // Sources
    Res: GrdRes,			// Resolution - segments per square (default = 512)
    Siz: GrdSiz,			// Size of Smallest Square = default = 3200m = 2 miles
    WSp: WndSpd,			// Wind Speed
    WHd: WndHdg,			// Wind Heading
    Chp: Choppy,			// default = 1
    // Results
    Dsp: 0,					// The Displacement Map
    Nrm: 0,					// The Normal Map
    NMS: new THREE.Vector2(1, 1), // Normal Map Scale (flip Y for left-handed maps)
    Spd: 1					// Animation Speed
  };
  //- External Values
  let wavSpd = 1;					// Animation speed - use in main program (lower is faster)
  let normalMapScale = vec2(1.0, 1.0);
  let WtrGeo, WtrMat, WtrMsh = 0;
  // Texture
  let ColSrc = "https://threejs.org/examples/textures/uv_grid_opengl.jpg";	// 1024
  let uvTexture = 0;
  // Planes
  let GrdRCs = 2;
  let GrdPtr = [0];
  let WavMZV = [0];
  let WavMXV = [0];

  //= Key Bindings ===============================================================
  let K_Paws = 80;				// Pause (p)
  let K_Wire = 87;				// Pause (p)

  /*= BASIC VALUES ============================================================*/
  let container = document.createElement('div');
  document.body.appendChild(container);
  //- Camera
  let camera = new THREE.PerspectiveCamera(55.0, window.innerWidth / window.innerHeight, 0.5, SkyLim);
  camera.position.set(0, 350, 800);
  //- Scene
  let scene = new THREE.Scene();
  //- Renderer
  let renderer = new THREE.WebGPURenderer({antialias: true});	//### r167
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setAnimationLoop(rendAll);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.autoUpdate = true;
  renderer.receiveShadow = true;
  renderer.shadowMap.type = THREE.BasicShadowMap;
  container.appendChild(renderer.domElement);
  //- Lights
  let sunLight = new THREE.DirectionalLight(SunCol, 3.0);
  sunLight.position.copy(SunPos).normalize();
  scene.add(sunLight);
  //- Controls
  let controls = new OrbitControls(camera, renderer.domElement);
  //- Inputs
  document.addEventListener("keydown", onDocumentKeyDown, false);
  //	document.addEventListener("keyup", onDocumentKeyUp, false);
  window.addEventListener("resize", onWindowResize, false);
  //- Loading Manager
  // Create a loading manager to set RESOURCES_LOADED when appropriate.
  // Pass loadingManager to all resource loaders.
  let loadingManager = new THREE.LoadingManager();
  let RESOURCES_LOADED = false;
  loadingManager.onLoad = function () {
    console.log("loaded all resources");
    RESOURCES_LOADED = true;
    initAll();
  };
  let txtrLoader = new THREE.TextureLoader(loadingManager);
  let cubeLoader = new THREE.CubeTextureLoader(loadingManager);

  /*= MAIN PROGRAM =============================================================*/
  loadAll();

  /*= 0 LOAD ALL ===============================================================*/
  function loadAll() {
    loadSkyBox();
    loadOceans();
  }

  /*= 1 INITIALIZE =============================================================*/
  function initAll() {
    initOceans(renderer, wav_);				// Initialize Ocean
    // Show stats
    if (StatOn) {							// show stats
      StatOn = new Stats({
        precision: 3,
        horizontal: false
      });
      StatOn.init(renderer);
      StatOn.domElement.style.cssText = "position:absolute;top:75%;left:95%;";
      container.appendChild(StatOn.dom);
    }
    LodFlg = 1;
  }

  /*= 2 RENDER =================================================================*/
  function rendAll() {
//	requestAnimationFrame(rendAll);
    if (PawsOn == 0 && LodFlg > 0) {
      // Update camera position
      if (camera.position.y < 0.0) {
        camera.position.y = 2.0;
      }
      if (!waves.update) {
        return
      }
      waves.update(); 					// Move Ocean
      controls.update();					// Controls
      if (StatOn) StatOn.update();		// Stats
    }
    renderer.renderAsync(scene, camera);	// ### WebGPU requires Async
    renderer.resolveTimestampsAsync(THREE.TimestampQuery.RENDER); // r173
  }

  /*= SKY BOX ==================================================================*/

  function loadSkyBox() {
    let envMap = cubeLoader
        .setPath(SBPath)
        .load(["px.jpg", "nx.jpg", "py.jpg", "ny.jpg", "pz.jpg", "nz.jpg"]);
    envMap.format = THREE.RGBAFormat;
    envMap.colorSpace = THREE.SRGBColorSpace;	// ### r152
    scene.background = envMap;
    LodFlg = 1;
  }

  //= OCEANS =====================================================================

  function loadOceans() {
//	uvTexture = new THREE.TextureLoader(loadingManager).load('https://threejs.org/examples/textures/uv_grid_opengl.jpg');
    uvTexture = txtrLoader.load('https://threejs.org/examples/textures/uv_grid_opengl.jpg');
  }

  function initOceans(renderer, wav_) {
    waves = new Ocean(renderer, wav_);
    WtrGeo = new THREE.PlaneGeometry(GrdSiz, GrdSiz, GrdSeg, GrdSeg);
    WtrGeo.rotateX(-Math.PI * 0.5);
    let color2 = new THREE.Color(WtrCol);
    WtrMat = new THREE.MeshStandardNodeMaterial({
      colorNode: color(color2),
      metalness: 0.5,												// Mine = 1.0, theirs = 0
      roughness: 0.1,												// Mine = 0.7, theirs = 0.1
      positionNode: positionLocal.add(texture(wav_.Dsp).xyz),
      normalNode: normalMap(texture(wav_.Nrm), normalMapScale),	// Animated texture
      envMap: scene.background,
      envMapIntensity: 1,
    });

    // Compute Starting Z and X Values
    let zx = -0.5 * (GrdRCs) * GrdSiz + 0.5 * GrdSiz;
    for (let i = 0; i < GrdRCs; i++) {
      WavMZV[i] = zx;
      WavMXV[i] = zx;
      zx = zx + GrdSiz;
    }
    // 4 Adjacent Planes
    let n = 0;
    for (let z = 0; z < GrdRCs; z++) {		// Row X2
      for (let x = 0; x < GrdRCs; x++) {	// Column X2
        GrdPtr[n] = new THREE.Mesh(WtrGeo, WtrMat);
        scene.add(GrdPtr[n]);
        GrdPtr[n].position.set(WavMXV[x], 0, -WavMZV[z]);
        n++;
      }
    }
  }

  /*= 4 MISC SUBROUTINES =======================================================*/

  //- Converts degrees to 360
  function Mod360(deg) {
    while (deg < 0) deg = deg + 360;	// Make deg a positive number
    deg = deg % 360;				// Compute remainder of any number divided by 360
    return deg;
  }

  //- Keyboard -------------------------------------------------------------------

  //- Key Down
  function onDocumentKeyDown(event) {
    let keyCode = event.which;
    if (event.keyCode == K_Paws) togglePaws();			// Pause
    if (event.keyCode == K_Wire) toggleWire();			// Wire
  }

  //- Key Up
  function onDocumentKeyUp(event) {
    let keyCode = event.which;
    //
  }

  //- Toggle Pause
  function togglePaws() {
    PawsOn = 1 - PawsOn;
  }

  //- Toggle Wire
  function toggleWire() {
    WireOn = 1 - WireOn;
    if (WireOn) {
      for (let x = 0; x < 16; x++) {
        WtrMat.wireframe = true;
        WtrMat.needsUpdate = true;
      }
    } else {
      for (let x = 0; x < 16; x++) {
        WtrMat.wireframe = false;
        WtrMat.needsUpdate = true;
      }
    }
  }

  //= Window Resize Input ========================================================
  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }

</script>

<div>
<!--  <canvas data-engine="three.js r179 webgpu" width="2560" height="825"-->
<!--          style="display: block; width: 2560px; height: 825px; touch-action: none;"></canvas>-->
  <div style="position: absolute; top: 75%; left: 95%;">
    <canvas width="90" height="48"
            style="width: 90px; height: 48px; position: absolute; display: block; left: 0px; top: 0px;"></canvas>
    <canvas width="90" height="48"
            style="width: 90px; height: 48px; position: absolute; display: block; left: 0px; top: 48px;"></canvas>
    <canvas width="90" height="48"
            style="width: 90px; height: 48px; position: absolute; display: block; left: 0px; top: 96px;"></canvas>
    <canvas width="90" height="48"
            style="width: 90px; height: 48px; position: absolute; display: block; left: 0px; top: 144px;"></canvas>
  </div>
</div>
</body>
</html>
